04-12-24 (Friday)

Lord, we pray for ourselves in our daily study and work.

We give thanks for the skills we already have; we pray for wise and good use of these skills in building your kingdom.

We pray for those thinking about changing their study and/or work - especially those unhappy or insecure; those feeling undervalued or unfulfilled; those feeling that are in the wrong place; those who can’t wait for 5pm on Friday.

We pray for those with no sense of direction or purpose or vocation, for those drifting,
for those wanting to do a particular job, but unable through disability, illness, lack of confidence or lack of opportunity;
for those who are unemployed.
We pray for those at school or college now making decisions which will affect our working lives. We pray for all Careers Advisers.

We pray for those who are teaching us;
for our colleagues,
for our administrators.

1 Incremental development

  • Some strategies (not necessarily exclusive):
    • Iterative development: Develop the software in a series of iterations, with each iteration adding new functionality or improving existing features based on feedback and requirements.
    • Divide and conquer: Break down the overall project into smaller, more manageable tasks or modules. Focus on completing and testing one module at a time before moving on to the next.
    • Test-Driven Development (TDD): Write tests for the desired functionality before writing the actual code. This ensures that the code meets the requirements and can be easily tested for correctness.
  • Some good practices:
    • Version control: track changes, collaborate with team members, and revert to previous versions if needed.
    • Code refactoring: rewrite parts of the code to improve its design, readability, and maintainability. Refactoring should be done incrementally to avoid introducing bugs.
    • Feature flags: code mechanisms to enable or disable new features in your program This allows you to gradually roll out new functionality and test it in a controlled manner.
    • Code stubs

1.1 Code stubs

  • Sometimes good programming is like in drawing: leave specific details to the end!

  • You can mark what should be done later with code stubs:

1.1.0.1 As comments

def divide_numbers(a, b):
    # FIXME: Handle division by zero
    return a / b

# Example usage
result = divide_numbers(10, 0)
print(result)
  • Later, you can search for the word “FIXME” in your code and start to “polish” these parts.

1.1.0.2 As output messages

def steps_to_calories(steps):
    print('FIXME: finish steps_to_calories')
    return -1

1.1.0.3 With the “pass” keyword

class Person():
  pass
  # FIXME implement constructor later
  
def ride_bike():
  pass

1.1.0.4 With exceptions

def get_points(num_points):
    """Get num_points from the user. Return a list of (x,y) tuples."""
    raise NotImplementedError

2 Unit testing

  • If you are dividing a big project into smaller parts, make sure you can test each part individually.
  • Python has a module called unittest that is very useful for that. You can use it to test functions, classes and modules.

2.1 Setting up test cases

  • You will make a subclass out of the unitest.TestCase class. See the example:
import unittest

def add(x, y):
    return x + y

class TestAddFunction(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(0, 0), 0)
        self.assertEqual(add(-1, 1), 0)

if __name__ == '__main__':
    unittest.main()
  • The function unittest.main() will create objects for every test case class you created, and then run it.
  • Usually, you will create methods inside your TestCase class with the name “test_” +
  • Inside these methods, you will use “assertion” statements to check for conditions. Some of them are:
Assertion Statement Description
assertEqual(a, b) Asserts that a is equal to b.
assertNotEqual(a, b) Asserts that a is not equal to b.
assertTrue(x) Asserts that x is True.
assertFalse(x) Asserts that x is False.
assertIs(a, b) Asserts that a and b are the same object (identity check).
assertIsNot(a, b) Asserts that a and b are not the same object.
assertIsNone(x) Asserts that x is None.
assertIsNotNone(x) Asserts that x is not None.
assertIn(a, b) Asserts that a is in b.
assertNotIn(a, b) Asserts that a is not in b.
assertIsInstance(a, b) Asserts that a is an instance of class b.
assertNotIsInstance(a, b) Asserts that a is not an instance of class b.
assertAlmostEqual(a, b) Asserts that a is approximately equal to b, within a certain tolerance.
assertNotAlmostEqual(a, b) Asserts that a is not approximately equal to b, within a certain tolerance.
assertGreater(a, b) Asserts that a is greater than b.
assertGreaterEqual(a, b) Asserts that a is greater than or equal to b.
assertLess(a, b) Asserts that a is less than b.
assertLessEqual(a, b) Asserts that a is less than or equal to b.
assertRegex(s, r) Asserts that the string s matches the regular expression r.
assertNotRegex(s, r) Asserts that the string s does not match the regular expression r.
  • If everything runs ok, your ouput will say it!

2.2 Another example: testing a class

import unittest

# User-defined class
class Circle:
    def __init__(self, radius):
        self.radius = radius

    def compute_area(self):
        return 3.14 * self.radius**2


# Class to test Circle
class TestCircle(unittest.TestCase):
    def test_compute_area(self):
        c = Circle(0)
        self.assertEqual(c.compute_area(), 0.0)

        c = Circle(5)
        self.assertEqual(c.compute_area(), 78.5)

    def test_will_fail(self):
        c = Circle(5)
        self.assertLess(c.compute_area(), 0)

if __name__ == "__main__":
    unittest.main()

2.3 Some testing tips

  • Keep tests simple, focused, and readable.

  • Ensure test cases are independent and do not rely on the state of other tests.

  • Sometimes you will have to set up and tear down a testing environment (setting some mockup variables, etc). Organize and point this clearly.

  • If you are testing functions or methods:

    • Test the function’s behavior under different input conditions. This includes testing for edge cases, such as empty inputs or boundary conditions.
  • If you are testing classes and objects:

    • Test its methods;
    • Test inheritance and method overriding if applicable.
  • If you are testing modules and packages:

    • Test its classes, test its functions;
    • Test any global variables or constants;
    • Consider using integration tests to test the interaction between different modules.

3 Risk and responsibility in software errors

  • Software systems may become complex and with various non-foreseen emergent behaviors… and errors…
  • Software, as any technology, always involves risk!

3.1 Some arguments about risks…

  • What do you think about…
  1. “Product X is already accepted. Product Y as a lower risk, so Y should be accepted.” (also known as BAT - best available technology)
  2. “Effect A of product X is not natural; so it shouldn’t be accepted.”
  3. “No scientifically demonstrable risk was shown with product X, so it should be accepted.”
  4. “Soon we will know about the risks of product X, so let’s wait and see.”
  5. “Benefits of product X are greater than its risks”.
  • Is it possible to measure risk? Is it possible to measure harm?

3.2 Risk contracts and the virtue of courage

  • Who is affected by the risk? Are they OK with that? Did we consult them? Are they really free in that consent?

Example: cars are risky. But, somehow, as a society, we agreed that is best to have them than not having them.

  • However, if no one wants to lose nothing, how can we agree about some risk?
    • Sometimes, virtues of courage and love for each other may need to guide this assessment.

3.3 Responsibility attribution in big teams

  • Suppose some code fails in production. Who is responsible?
    • The one who idealized the product?
    • The one who programmed?
    • A library which was used and had bugs?
    • The one who tested the code?
    • The one who distributed the code?
  • Sometimes, finding the one to blame is very hard!
  • A well-debated problem (see Moral Responsibility and the Problem of Many Hands)

The problem of many hands (PMH) occurs if a collective is morally responsible for some result, whereas none of the individuals making up the collective is morally responsible (in the same degree) for this result.

  • How can genuine virtues of justice and wisdom guide us in that?